﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml.Linq;
#endregion

namespace DirectZZT.Shared.Core
{
    /// <summary>
    /// Contains exports.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Returns the working directory of this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The working directory of this assembly.</returns>
        public static string GetWorkingDirectory(this Assembly assembly)
        {
            return Path.GetDirectoryName(assembly.Location);
        }

        /// <summary>
        /// Returns the company of this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The company of this assembly.</returns>
        public static string GetCompany(this Assembly assembly)
        {
            AssemblyCompanyAttribute[] v = (AssemblyCompanyAttribute[])assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);
            if (v.Length == 1)
            {
                return v[0].Company;
            }

            return null;
        }

        /// <summary>
        /// Returns the version of this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The version of this assembly.</returns>
        public static string GetVersion(this Assembly assembly)
        {
            AssemblyFileVersionAttribute[] v = (AssemblyFileVersionAttribute[])assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false);
            if (v.Length == 1)
            {
                return v[0].Version;
            }

            return null;
        }

        /// <summary>
        /// Returns the configuration text of this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The configuration text of this assembly.</returns>
        public static string GetConfiguration(this Assembly assembly)
        {
            AssemblyConfigurationAttribute[] v = (AssemblyConfigurationAttribute[])assembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false);
            if (v.Length == 1)
            {
                return v[0].Configuration;
            }

            return null;
        }

        /// <summary>
        /// Returns the title information of this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The title information of this assembly.</returns>
        public static string GetTitle(this Assembly assembly)
        {
            AssemblyTitleAttribute[] v = (AssemblyTitleAttribute[])assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
            if (v.Length == 1)
            {
                return v[0].Title;
            }

            return null;
        }

        /// <summary>
        /// Returns the relative path of a file that lies in the same directory as this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <param name="absolute">The path to make relative.</param>
        /// <returns>Te relative path of a file that lies in the same directory as this assembly.</returns>
        public static string GetRelativePath(this Assembly assembly, string absolute)
        {
            return absolute.Replace(GetWorkingDirectory(assembly), "");
        }

        /// <summary>
        /// Returns the absolute path of a file that lies in the same directory as this assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <param name="relative">The path to make absolute.</param>
        /// <returns>Te absolute path of a file that lies in the same directory as this assembly.</returns>
        public static string GetAbsolutePath(this Assembly assembly, string relative)
        {
            return Path.Combine(GetWorkingDirectory(assembly), relative);
        }

        /// <summary>
        /// Clears the contents of a StringBuilder.
        /// </summary>
        /// <param name="stringBuilder"></param>
        public static void Clear(this StringBuilder stringBuilder)
        {
            // is this slower?
            stringBuilder.Remove(0, stringBuilder.Length);
        }

        /// <summary>
        /// Reads a Big-Endian UInt16 from the given BinaryReader.
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static ushort ReadUInt16BigEndian(this BinaryReader reader)
        {
            byte[] buffer = new byte[] { reader.ReadByte(), reader.ReadByte() };
            Array.Reverse(buffer);
            return BitConverter.ToUInt16(buffer, 0);
        }

        /// <summary>
        /// Reads a Big-Endian Int16 from the given BinaryReader.
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static short ReadInt16BigEndian(this BinaryReader reader)
        {
            byte[] buffer = new byte[] { reader.ReadByte(), reader.ReadByte() };
            Array.Reverse(buffer);
            return BitConverter.ToInt16(buffer, 0);
        }

        /// <summary>
        /// Tries to convert the integer value inside the string, and returns a default value if it didn't work.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static int TryGetInt32(this string value, int defaultValue)
        {
            int v = defaultValue;
            int.TryParse(value, out v);
            return v;
        }

        #region Collections

        /// <summary>
        /// Returns the last item of a list.
        /// If there weren't any items in the list, then the default value of <typeparamref name="T"/> is used.
        /// </summary>
        /// <typeparam name="T">The type of the list.</typeparam>
        /// <param name="list">The list to get the last item from.</param>
        /// <returns>The last item of a list. If there weren't any items in the list, then the default value of <typeparamref name="T"/> is used.</returns>
        public static T GetLast<T>(this IList<T> list)
        {
            if (list.Count >= 1)
            {
                return list[list.Count - 1];
            }
            else
            {
                return default(T);
            }
        }

        /// <summary>
        /// Removes the last item of a list.
        /// </summary>
        /// <typeparam name="T">The type of the list.</typeparam>
        /// <param name="list">The item to remove its last item.</param>
        public static void RemoveLast<T>(this IList<T> list)
        {
            if (list.Count > 1)
            {
                list.RemoveAt(list.Count - 1);
            }
            else
            {
                list.RemoveAt(0);
            }
        }

        #endregion

        #region String

        /// <summary>
        /// Provides the right-side equivalent of the Remove command.
        /// </summary>
        /// <param name="value">The string to act upon.</param>
        /// <param name="length">The amount of chars to delete from the right.</param>
        /// <returns>A new string that has the right side removed by the given <paramref name="length"/>.</returns>
        public static string RemoveRight(this string value, int length)
        {
            // it must at least be possible
            if (string.IsNullOrWhiteSpace(value))
            {
                return value;
            }
            return value.Remove(value.Length - length, length);
        }

        #endregion

        #region XDocument

        /// <summary>
        /// Tries to get the element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static string TryGetElementValue(this XElement element, string elementName, string defaultValue)
        {
            XElement e = element.Element(elementName);
            if (e != null)
            {
                return e.Value;
            }
            return defaultValue;
        }

        /// <summary>
        /// Tries to get the bool element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static bool TryGetElementValue(this XElement element, string elementName, bool defaultValue)
        {
            string value = TryGetElementValue(element, elementName, defaultValue.ToString());
            bool returnValue = false;
            bool.TryParse(value, out returnValue);
            return returnValue;
        }

        /// <summary>
        /// Tries to get the int element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static int TryGetElementValue(this XElement element, string elementName, int defaultValue)
        {
            string value = TryGetElementValue(element, elementName, defaultValue.ToString());
            int returnValue = -1;
            int.TryParse(value, out returnValue);
            return returnValue;
        }

        /// <summary>
        /// Tries to get the float element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static float TryGetElementValue(this XElement element, string elementName, float defaultValue)
        {
            string value = TryGetElementValue(element, elementName, defaultValue.ToString());
            float returnValue = -1.0f;
            float.TryParse(value, out returnValue);
            return returnValue;
        }

        /// <summary>
        /// Tries to get the attribute value of an attribute from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the attribute value from.</param>
        /// <param name="attributeName">The name of the attribute to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the attribute did not exist.</param>
        /// <returns></returns>
        public static string TryGetAttributeValue(this XElement element, string attributeName, string defaultValue)
        {
            XAttribute attribute = element.Attribute(attributeName);
            if (attribute != null)
            {
                return attribute.Value;
            }
            return defaultValue;
        }

        /// <summary>
        /// Tries to get the boolean element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static bool TryGetAttributeValue(this XElement element, string attributeName, bool defaultValue)
        {
            string value = TryGetAttributeValue(element, attributeName, defaultValue.ToString());
            bool returnValue = false;
            bool.TryParse(value, out returnValue);
            return returnValue;
        }

        /// <summary>
        /// Tries to get the integer element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static int TryGetAttributeValue(this XElement element, string attributeName, int defaultValue)
        {
            string value = TryGetAttributeValue(element, attributeName, defaultValue.ToString());
            int returnValue = -1;
            int.TryParse(value, out returnValue);
            return returnValue;
        }

        /// <summary>
        /// Tries to get the float element value of an element from the requested XElement.
        /// </summary>
        /// <param name="element">The XElement to get the element value from.</param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return, if the element did not exist.</param>
        /// <returns></returns>
        public static float TryGetAttributeValue(this XElement element, string attributeName, float defaultValue)
        {
            string value = TryGetAttributeValue(element, attributeName, defaultValue.ToString());
            float returnValue = -1.0f;
            float.TryParse(value, out returnValue);
            return returnValue;
        }

        /// <summary>
        /// Adds or overwrites the elements value.
        /// </summary>
        /// <param name="elements">The list to which to possibly add.</param>
        /// <param name="elementName">The name of the element to add or overwrite.</param>
        /// <param name="value">The value to add or overwrite.</param>
        public static void AddOrOverwriteValue(this IList<XElement> elements, string elementName, string value)
        {
            if (!elements.Any(e => string.Equals(e.Name.LocalName, elementName, StringComparison.InvariantCulture)))
            {
                // add value
                elements.Add(new XElement(elementName, value));
            }
            else
            {
                // overwrite value
                elements.First(e => string.Equals(e.Name.LocalName, elementName, StringComparison.InvariantCulture)).Value = value;
            }
        }

        /// <summary>
        /// Looks up whether or not an element with the given name exists in the list of elements and returns its value then.
        /// </summary>
        /// <param name="elements"></param>
        /// <param name="elementName">The name of the element to get its value.</param>
        /// <param name="defaultValue">The default value to return if the element didn't exist.</param>
        /// <returns></returns>
        public static string TryGetElementValue(this IList<XElement> elements, string elementName, string defaultValue)
        {
            var el = elements.FirstOrDefault(e => string.Equals(e.Name.LocalName, elementName, StringComparison.InvariantCulture));
            if (el != null)
            {
                return el.Value;
            }
            return defaultValue;
        }

        #endregion

    }
}
